#include "stdafx.h"
#include "InstanceQuad2.h"
#include "Camera2D.h"
#include <vector>

glm::mat4 ViewMatrix;
glm::mat4 ProjectionMatrix;

// Initial position : on +Z
glm::vec3 position = glm::vec3(0, 0, 2);
// Initial horizontal angle : toward -Z
float horizontalAngle = 3.14f;
// Initial vertical angle : none
float verticalAngle = 0.0f;
float speed = 10.0f; // 3 units / second
float mouseSpeed = 0.005f;

void computeMatricesFromInputs()
{
	// Direction : Spherical coordinates to Cartesian coordinates conversion
	glm::vec3 direction(
		cos(verticalAngle) * sin(horizontalAngle),
		sin(verticalAngle),
		cos(verticalAngle) * cos(horizontalAngle)
		);

	// Right vector
	glm::vec3 right = glm::vec3(
		sin(horizontalAngle - 3.14f / 2.0f),
		0,
		cos(horizontalAngle - 3.14f / 2.0f)
		);

	// Up vector
	glm::vec3 up = glm::cross(right, direction);
	
	ProjectionMatrix = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
	ViewMatrix = glm::lookAt(position, position + direction, up);
}

// CPU representation of a particle
struct Particle
{
	glm::vec3 pos, speed;
	unsigned char r, g, b, a; // Color
	float size, angle, weight;
	float life; // Remaining life of the particle. if <0 : dead and unused.
	float cameradistance; // *Squared* distance to the camera. if dead : -1.0f
	
	bool operator<(const Particle& that) const
	{
		// Sort in reverse order : far particles drawn first.
		return this->cameradistance > that.cameradistance;
	}
};

const int MaxParticles = 1000;
Particle ParticlesContainer[MaxParticles];
int LastUsedParticle = 0;

static GLfloat g_particule_position_size_data [MaxParticles * 4];
static GLubyte g_particule_color_data[MaxParticles * 4];

// Finds a Particle in ParticlesContainer which isn't used yet.
// (i.e. life < 0);
int FindUnusedParticle()
{
	for ( int i = LastUsedParticle; i < MaxParticles; i++ )
	{
		if ( ParticlesContainer[i].life < 0 )
		{
			LastUsedParticle = i;
			return i;
		}
	}
	
	for ( int i = 0; i < LastUsedParticle; i++ )
	{
		if ( ParticlesContainer[i].life < 0 )
		{
			LastUsedParticle = i;
			return i;
		}
	}
	
	return 0; // All particles are taken, override the first one
}

void SortParticles()
{
	std::sort(&ParticlesContainer[0], &ParticlesContainer[MaxParticles]);
}

double lastTime;

GLuint billboard_vertex_buffer;
GLuint particles_position_buffer;
GLuint particles_color_buffer;

namespace a2d
{
//------------------------------------------------------------------
	
	sint TextureID;
	sint CameraRight_worldspace_ID;
	sint CameraUp_worldspace_ID;
	sint ViewProjMatrixID;

	GLsizei const VertexCount = 6;
	GLsizeiptr const PositionSize = VertexCount * sizeof(glm::vec2);
	glm::vec2 const PositionData[VertexCount] =
	{
		glm::vec2(-0.5f, -0.5f),
		glm::vec2(0.5f, -0.5f),
		glm::vec2(0.5f, 0.5f),
		glm::vec2(0.5f, 0.5f),
		glm::vec2(-0.5f, 0.5f),
		glm::vec2(-0.5f, -0.5f)
	};
	GLsizeiptr const ColorSize = 2 * sizeof(glm::vec4);
	glm::vec4 const ColorData[2] =
	{
		glm::vec4(1.0f, 0.0f, 1.0f, 1.0f),
		glm::vec4(0.0f, 0.5f, 1.0f, 1.0f)
	};

	GLsizei const InstanceCount = 2;

	GLuint Color2;

	VAO InstanceQuad2::m_vao;
	ShaderProgram InstanceQuad2::m_shaders;

	bool InstanceQuad2::Create()
	{
		if ( !m_vao.Create() )
			return false;
		m_vao.Bind();

		if ( !m_shaders.Load("../Particle.vertexshader", "../Particle.fragmentshader") )
			return false;

		// Vertex shader
		CameraRight_worldspace_ID = glGetUniformLocation(m_shaders.GetProgramID(), "CameraRight_worldspace");
		CameraUp_worldspace_ID = glGetUniformLocation(m_shaders.GetProgramID(), "CameraUp_worldspace");
		ViewProjMatrixID = glGetUniformLocation(m_shaders.GetProgramID(), "VP");
		// fragment shader
		TextureID = glGetUniformLocation(m_shaders.GetProgramID(), "myTextureSampler");
		
		for ( int i = 0; i < MaxParticles; i++ )
		{
			ParticlesContainer[i].life = -1.0f;
			ParticlesContainer[i].cameradistance = -1.0f;
		}

		if ( !m_tex.Create("../Test/dragon.png") )
			return false;

		static const GLfloat g_vertex_buffer_data[] = 
		{
			-0.5f, -0.5f, 0.0f,
			0.5f, -0.5f, 0.0f,
			-0.5f, 0.5f, 0.0f,
			0.5f, 0.5f, 0.0f,
		};
		
		glGenBuffers(1, &billboard_vertex_buffer);
		glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
		glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

		// The VBO containing the positions and sizes of the particles
		glGenBuffers(1, &particles_position_buffer);
		glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
		// Initialize with empty (NULL) buffer : it will be updated later, each frame.
		glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW);
		
		// The VBO containing the colors of the particles
		glGenBuffers(1, &particles_color_buffer);
		glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
		// Initialize with empty (NULL) buffer : it will be updated later, each frame.
		glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW);

		lastTime = glfwGetTime();
		
		/*m_vbo.Create();
		m_vbo.Bind();
		m_vbo.SetData(PositionSize, PositionData);
		m_vbo.ClearLocAtrib();
		m_vbo.SetOneVertexAtrib(2, 2);
		m_vbo.EnableAtrib();
		glVertexAttribDivisor(0, 0);

		m_pvw_mat = glGetUniformLocation(m_shaders.GetProgramID(), "MVPinst");

		glGenBuffers(1, &Color2);
		glBindBuffer(GL_ARRAY_BUFFER, Color2);
		glBufferData(GL_ARRAY_BUFFER, ColorSize, ColorData, GL_STATIC_DRAW);
		glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, 0);
		glVertexAttribDivisor(1, 1);
		glEnableVertexAttribArray(1);*/

		return true;
	}

	void InstanceQuad2::Draw()
	{
		m_tex.Bind();
		m_vao.Bind();
		//m_vbo.Bind();
		m_shaders.Bind();

		double currentTime = glfwGetTime();
		double delta = currentTime - lastTime;
		lastTime = currentTime;

		computeMatricesFromInputs();
		
		// We will need the camera's position in order to sort the particles
		// w.r.t the camera's distance.
		// There should be a getCameraPosition() function in common/controls.cpp,  but this works too.
		glm::vec3 CameraPosition(glm::inverse(ViewMatrix)[3]);		
		glm::mat4 ViewProjectionMatrix = ProjectionMatrix * ViewMatrix;

		// Generate 10 new particule each millisecond, but limit this to 16 ms (60 fps), or if you have 1 long frame (1sec), newparticles will be huge and the next frame even longer.
		int newparticles = (int)(delta*10000.0);
		if ( newparticles > (int)(0.016f*10000.0) )
			newparticles = (int)(0.016f*10000.0);

		for ( int i = 0; i < newparticles; i++ )
		{
			int particleIndex = FindUnusedParticle();
			ParticlesContainer[particleIndex].life = 5.0f; // This particle will live 5 seconds.
			ParticlesContainer[particleIndex].pos = glm::vec3(0, 0, -20.0f);
			
			float spread = 1.5f;
			glm::vec3 maindir = glm::vec3(0.0f, 10.0f, 0.0f);
			// Very bad way to generate a random direction; 
			// See for instance http://stackoverflow.com/questions/5408276/python-uniform-spherical-distribution instead, combined with some user-controlled parameters (main direction, spread, etc)
			glm::vec3 randomdir = glm::vec3(
				(rand() % 2000 - 1000.0f) / 1000.0f,
				(rand() % 2000 - 1000.0f) / 1000.0f,
				(rand() % 2000 - 1000.0f) / 1000.0f
				);

			ParticlesContainer[particleIndex].speed = maindir + randomdir*spread;
			
			// Very bad way to generate a random color
			ParticlesContainer[particleIndex].r = rand() % 256;
			ParticlesContainer[particleIndex].g = rand() % 256;
			ParticlesContainer[particleIndex].b = rand() % 256;
			ParticlesContainer[particleIndex].a = (rand() % 256) / 3;
			
			ParticlesContainer[particleIndex].size = (rand() % 1000) / 2000.0f + 0.1f;
		}

		// Simulate all particles
		int ParticlesCount = 0;
		for ( int i = 0; i<MaxParticles; i++ )
		{
			Particle& p = ParticlesContainer[i]; // shortcut
			
			if ( p.life > 0.0f )
			{
				// Decrease life
				p.life -= delta;
				if ( p.life > 0.0f )
				{
					// Simulate simple physics : gravity only, no collisions
					p.speed += glm::vec3(0.0f, -9.81f, 0.0f) * (float)delta * 0.5f;
					p.pos += p.speed * (float)delta;
					p.cameradistance = glm::length2(p.pos - CameraPosition);

					// Fill the GPU buffer
					g_particule_position_size_data[4 * ParticlesCount + 0] = p.pos.x;
					g_particule_position_size_data[4 * ParticlesCount + 1] = p.pos.y;
					g_particule_position_size_data[4 * ParticlesCount + 2] = p.pos.z;

					g_particule_position_size_data[4 * ParticlesCount + 3] = p.size;

					g_particule_color_data[4 * ParticlesCount + 0] = p.r;
					g_particule_color_data[4 * ParticlesCount + 1] = p.g;
					g_particule_color_data[4 * ParticlesCount + 2] = p.b;
					g_particule_color_data[4 * ParticlesCount + 3] = p.a;
				}
				else
				{
					// Particles that just died will be put at the end of the buffer in SortParticles();
					p.cameradistance = -1.0f;
				}
				
				ParticlesCount++;
			}
		}

		SortParticles();

		// Update the buffers that OpenGL uses for rendering.
		// There are much more sophisticated means to stream data from the CPU to the GPU, but this is outside the scope of this tutorial.
		// http://www.opengl.org/wiki/Buffer_Object_Streaming
		
		glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
		glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLfloat), NULL, GL_STREAM_DRAW); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
		glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLfloat)* 4, &g_particule_position_size_data);
		
		glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
		glBufferData(GL_ARRAY_BUFFER, MaxParticles * 4 * sizeof(GLubyte), NULL, GL_STREAM_DRAW); // Buffer orphaning, a common way to improve streaming perf. See above link for details.
		glBufferSubData(GL_ARRAY_BUFFER, 0, ParticlesCount * sizeof(GLubyte)* 4, &g_particule_color_data);
						
		// Set our "myTextureSampler" sampler to user Texture Unit 0
		glUniform1i(TextureID, 0);
		
		// Same as the billboards tutorial
		glUniform3f(CameraRight_worldspace_ID, ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]);
		glUniform3f(CameraUp_worldspace_ID, ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]);		
		glUniformMatrix4fv(ViewProjMatrixID, 1, GL_FALSE, &ViewProjectionMatrix[0][0]);
		
		// 1rst attribute buffer : vertices
		glEnableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer);
		glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);

		// 2nd attribute buffer : positions of particles' centers
		glEnableVertexAttribArray(1);
		glBindBuffer(GL_ARRAY_BUFFER, particles_position_buffer);
		glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)0);


		// 3rd attribute buffer : particles' colors
		glEnableVertexAttribArray(2);
		glBindBuffer(GL_ARRAY_BUFFER, particles_color_buffer);
		glVertexAttribPointer(2, 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, (void*)0);


		// These functions are specific to glDrawArrays*Instanced*.
		// The first parameter is the attribute buffer we're talking about.
		// The second parameter is the "rate at which generic vertex attributes advance when rendering multiple instances"
		// http://www.opengl.org/sdk/docs/man/xhtml/glVertexAttribDivisor.xml
		glVertexAttribDivisor(0, 0); // particles vertices : always reuse the same 4 vertices -> 0
		glVertexAttribDivisor(1, 1); // positions : one per quad (its center)                 -> 1
		glVertexAttribDivisor(2, 1); // color : one per quad                                  -> 1
		
		// Draw the particules !
		// This draws many times a small triangle_strip (which looks like a quad).
		// This is equivalent to :
		// for(i in ParticlesCount) : glDrawArrays(GL_TRIANGLE_STRIP, 0, 4), 
		// but faster.
		glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, ParticlesCount);
		
		glDisableVertexAttribArray(0);
		glDisableVertexAttribArray(1);
		glDisableVertexAttribArray(2);




		//glm::mat4 Model[2];
		//Model[0] = glm::translate(glm::mat4(1.0f), glm::vec3(-0.5f, 0.3f, 0.0f));
		//Model[1] = glm::translate(glm::mat4(1.0f), glm::vec3(0.5f, -0.3f, 0.0f));
		//glUniformMatrix4fv(m_pvw_mat, 2, GL_FALSE, &Model[0][0][0]);

		//glDrawArraysInstanced(GL_TRIANGLES, 0, VertexCount, 2);
	}

	void InstanceQuad2::Destroy()
	{
		m_tex.Destroy();
		//m_vbo.Destroy();
		m_shaders.Destroy();
		m_vao.Destroy();
	}

//------------------------------------------------------------------
} // namespace a2d
